package cn.cas.mango.service;

import cn.cas.mango.dto.param.AggParam;
import cn.cas.mango.dto.param.HotKeywords;
import cn.cas.mango.dto.param.News;
import cn.cas.mango.util.CheckParam;
import cn.cas.mango.util.StringUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch.core.GetRequest;
import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.MgetRequest;
import co.elastic.clients.elasticsearch.core.MgetResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
import co.elastic.clients.elasticsearch.core.search.Hit;

import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.json.Json;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.get.MultiGetRequest;
import org.elasticsearch.client.RequestOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

@Service
@Slf4j
public class Agg {
  @Value("${elasticsearch.newIndex}")
  private String index;
  @Value("${elasticsearch.clusterIndex}")
  private String CLUSTERINDEX;

  @Autowired
  private ElasticsearchClient client;
  public JSONObject agg(AggParam aggParam) {
    CheckParam.checkAgg(aggParam);

    Integer aggSize = aggParam.getSize();
    Integer groupSize = 15000;
    SearchRequest.Builder builder = new SearchRequest.Builder();
    builder.index(index).
        trackTotalHits(k->k.enabled(true))
//        .query(q->q.bool(x->x.filter()))
//        .aggregations("cluster_group_low", y -> y.terms(m -> m.field("cluster_id1").size(groupSize)))
        .aggregations("cluster_group_mid", y -> y.terms(m -> m.field("cluster_id3").size(groupSize)))
//        .aggregations("cluster_group_high", y -> y.terms(m -> m.field("cluster_id5").size(groupSize)))
    ;

    SearchRequest build = getSearchRequest(aggParam, builder).build();

    log.info("agg {}", build);
    JSONObject jsonObject = new JSONObject();
    try {
      SearchResponse<Map> search = client.search(build, Map.class);
      Map<String, Aggregate> aggregations = search.aggregations();
      Set<String> keys = aggregations.keySet();
      JSONObject cluster = new JSONObject();

      for (String key: keys) {
        Aggregate aggregate = aggregations.get(key);

        List<StringTermsBucket> array = aggregate.sterms().buckets().array();
        JSONArray jsonArray = new JSONArray();
        int arraySize = array.size();
        int num = (int) array.stream().filter(x -> x.docCount() > 1).count();

        for (int i = 0; i < arraySize && i < aggSize; i++) {
          StringTermsBucket stringTermsBucket = array.get(i);
          JSONObject jsonObject1 = new JSONObject();
          if (!aggParam.getShowAll())
            jsonObject1.put((String) stringTermsBucket.key()._get(), stringTermsBucket.docCount());
          else {
            String clusterId = (String) stringTermsBucket.key()._get();
            aggParam.setNewsClusterId(clusterId);
            JSONObject clusterData = new JSONObject();
            clusterData.put("count", stringTermsBucket.docCount());
            JSONArray objects = showOne(aggParam);
            clusterData.put("data", objects);
            JSONObject first = (JSONObject)objects.get(0);
            clusterData.put("province", first.get("source"));
            clusterData.put("region", first.get("region"));
            clusterData.put("title", first.get("title"));
            clusterData.put("countryOrArea", first.get("countryOrArea"));
            jsonObject1.put(clusterId, clusterData);
          }

          jsonArray.add(jsonObject1);

        }
        JSONObject temp = new JSONObject();
        temp.put("a_count", arraySize);
        temp.put("a_count>1", num);
        temp.put("data", jsonArray);
        cluster.put(key, temp);
      }
      jsonObject.put("groups", cluster);
      return
          jsonObject;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public JSONObject agg2(AggParam aggParam) {
    CheckParam.checkAgg(aggParam);

    Integer aggSize = aggParam.getSize();
    Integer groupSize = 15000;
    SearchRequest.Builder builder = new SearchRequest.Builder();
    builder.index(index).
        trackTotalHits(k->k.enabled(true))
//        .query(q->q.bool(x->x.filter()))
//        .aggregations("cluster_group_low", y -> y.terms(m -> m.field("cluster_id1").size(groupSize)))
        .aggregations("cluster_group_mid", y -> y.terms(m -> m.field("cluster_id3").size(groupSize)))
//        .aggregations("cluster_group_high", y -> y.terms(m -> m.field("cluster_id5").size(groupSize)))
    ;

    SearchRequest build = getSearchRequest(aggParam, builder).build();

    log.info("agg {}", build);
    JSONObject jsonObject = new JSONObject();
    try {
      SearchResponse<Map> search = client.search(build, Map.class);
      Map<String, Aggregate> aggregations = search.aggregations();
      Set<String> keys = aggregations.keySet();
      JSONObject cluster = new JSONObject();

      for (String key: keys) {
        Aggregate aggregate = aggregations.get(key);

        List<StringTermsBucket> array = aggregate.sterms().buckets().array();
        JSONArray jsonArray = new JSONArray();
        int arraySize = array.size();
        int num = (int) array.stream().filter(x -> x.docCount() > 1).count();

        for (int i = 0; i < arraySize && i < aggSize; i++) {
          StringTermsBucket stringTermsBucket = array.get(i);

          String clusterId = (String) stringTermsBucket.key()._get();
          aggParam.setNewsClusterId(clusterId);
          jsonArray.add(showOne(aggParam));

        }
        JSONObject temp = new JSONObject();
        temp.put("a_count", arraySize);
        temp.put("a_count>1", num);
        temp.put("data", jsonArray);
        cluster.put(key, temp);
      }
      jsonObject.put("groups", cluster);
      return
          jsonObject;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private SearchRequest.Builder getSearchRequest(AggParam aggParam, SearchRequest.Builder builder) {
    if (aggParam.getStartDate() != null) {
      builder.query(q->q.bool(b->b.filter(f->f.range(r->r.field("fssj").gte(JsonData.of(aggParam.getStartDate()))
          .lte(JsonData.of(aggParam.getEndDate()))))));
    }
    return builder;
  }

  private List<Hit<News>> getOne(AggParam aggParam) {
    CheckParam.checkAgg(aggParam);
    SearchRequest.Builder builder = new SearchRequest.Builder();
    builder.index(index).query(q -> q.bool(b->{
      if (aggParam.getStartDate() == null)
        return b.must(k->k.term(
          t -> t.field(aggParam.getGetField())
              .value(aggParam.getNewsClusterId())));
      else {
        return b.must(k-> k.range(r->r.field("fssj").gte(JsonData.of(aggParam.getStartDate()))
            .lte(JsonData.of(aggParam.getEndDate())))).must(k->k.term(
            t -> t.field(aggParam.getGetField())
                .value(aggParam.getNewsClusterId())));
      }
    })).size(aggParam.getOneSize()).sort(s->s.field(FieldSort.of(f->f.field("fssj").order(SortOrder.Asc))));
        ;
    SearchRequest build = builder.build();
//    log.info("get one {}", build);
    try {
      return client.search(build, News.class).hits().hits();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }


  public JSONArray showOne(AggParam aggParam) {
    List<Hit<News>> one = getOne(aggParam);
    JSONArray jsonArray = new JSONArray();
    for (Hit<News> hit: one) {
      News news = hit.source();
      JSONObject jsonObject = new JSONObject();
      assert news != null;
      jsonObject.put("title", news.getSjmc());
      jsonObject.put("content", news.getSjms());
      if (aggParam.getKeywords() != null && aggParam.getKeywords())
        jsonObject.put("keywords", new HotKeywords(news.getKeywords()).getKeywords());
      jsonObject.put("date", news.getFssj());
      jsonObject.put("source", news.getLyss());
      jsonObject.put("region", news.getSsly());
      jsonObject.put("countryOrArea", news.getCountryOrArea());
      jsonObject.put(aggParam.getGetField(), aggParam.getNewsClusterId());
      jsonArray.add(jsonObject);
    }
    return jsonArray;
  }

  private void getBoth(JSONArray retArray, JSONArray jsonArray) {
    Map<String, List<JSONObject>> storeRegion = new HashMap<>();
    // 按照region聚合
    for (Object o: jsonArray) {
      JSONObject jsonObject = (JSONObject) o;
      String s = new ArrayList<>(jsonObject.keySet()).get(0);
      JSONObject clusterData = (JSONObject)jsonObject.get(s);
      String region = (String) clusterData.get("region");
      if (!storeRegion.containsKey(region)) {
        storeRegion.put(region, new ArrayList<>());
      }
      storeRegion.get(region).add(clusterData);
    }
    // 按照国别聚合

    for (String region: storeRegion.keySet()) {
      Map<String, List<JSONObject>> countryOrAreaMap = new HashMap<>();
      JSONObject regionObject = new JSONObject();
      JSONArray regionArray = new JSONArray();
      List<JSONObject> jsonObjects = storeRegion.get(region);
      for (JSONObject jsonObject: jsonObjects) {
        String countryOrArea = (String) jsonObject.get("countryOrArea");
        if (!countryOrAreaMap.containsKey(countryOrArea)) {
          countryOrAreaMap.put(countryOrArea, new ArrayList<>());
        }
        countryOrAreaMap.get(countryOrArea).add(jsonObject);
      }
      for (String countryOrArea: countryOrAreaMap.keySet()) {
        JSONObject nowCountryOrArea = new JSONObject();
        nowCountryOrArea.put("name", countryOrArea);
        nowCountryOrArea.put("children", wrapCountryOrArea(countryOrAreaMap.get(countryOrArea)));
        regionArray.add(nowCountryOrArea);
      }
      regionObject.put("name", region);
      regionObject.put("children", regionArray);
      retArray.add(regionObject);
    }
  }

  private void getCountryOrArea(JSONArray retArray, JSONArray jsonArray) {
    Map<String, List<JSONObject>> countryOrAreaMap = new HashMap<>();
    for (Object o: jsonArray) {
      JSONObject jsonObject = (JSONObject) o;
      String s = new ArrayList<>(jsonObject.keySet()).get(0);
      JSONObject clusterData = (JSONObject)jsonObject.get(s);
      String countryOrArea = (String) clusterData.get("countryOrArea");
      if (!countryOrAreaMap.containsKey(countryOrArea)) {
        countryOrAreaMap.put(countryOrArea, new ArrayList<>());
      }
      countryOrAreaMap.get(countryOrArea).add(clusterData);
    }

    for (String countryOrArea: countryOrAreaMap.keySet()) {
      JSONObject nowCountryOrArea = new JSONObject();
      nowCountryOrArea.put("name", countryOrArea);
      nowCountryOrArea.put("children", wrapCountryOrArea(countryOrAreaMap.get(countryOrArea)));
      retArray.add(nowCountryOrArea);
    }
  }

  private void getRegion(JSONArray retArray, JSONArray jsonArray) {
    Map<String, List<JSONObject>> storeRegion = new HashMap<>();
    // 按照region聚合
    for (Object o: jsonArray) {
      JSONObject jsonObject = (JSONObject) o;
      String s = new ArrayList<>(jsonObject.keySet()).get(0);
      JSONObject clusterData = (JSONObject)jsonObject.get(s);
      String region = (String) clusterData.get("region");
      if (!storeRegion.containsKey(region)) {
        storeRegion.put(region, new ArrayList<>());
      }
      storeRegion.get(region).add(clusterData);
    }
    for (String region: storeRegion.keySet()) {
      JSONObject nowCountryOrArea = new JSONObject();
      nowCountryOrArea.put("name", region);
      nowCountryOrArea.put("children", wrapCountryOrArea(storeRegion.get(region)));
      retArray.add(nowCountryOrArea);
    }

  }

  public JSONObject getD3(AggParam aggParam) {
    JSONObject ret = new JSONObject();
    ret.put("name", "flare");
    aggParam.setSize(1000000);
    JSONObject agg = agg(aggParam);
    JSONArray jsonArray = (JSONArray)((JSONObject)((JSONObject)agg.get("groups")).get("cluster_group_mid")).get("data");
    JSONArray retArray = new JSONArray();
    ret.put("children", retArray);
    if (aggParam.getClusterType().equals("all")) {
     getBoth(retArray, jsonArray);
    } else if (aggParam.getClusterType().equals("region")){
      getRegion(retArray, jsonArray);
    } else {
      getCountryOrArea(retArray, jsonArray);
    }

    return ret;
  }

  private JSONObject wrapCluster(JSONObject cluster) {
    String clusterName = StringUtil.getCutName((String) cluster.get("title"), 10);
    JSONObject ret = new JSONObject();
    ret.put("name", clusterName);
    JSONArray jsonArray = new JSONArray();
    for (Object o: (JSONArray)cluster.get("data")) {
      JSONObject member = (JSONObject) o;
      JSONObject wrappedMember = new JSONObject();
      wrappedMember.put("name", StringUtil.getCutName((String) member.get("title"), 10));
      wrappedMember.put("value", 1);
      jsonArray.add(wrappedMember);
    }
    ret.put("children", jsonArray);
    return ret;
  }

  private JSONArray wrapCountryOrArea(List<JSONObject> jsonObjects) {
    JSONArray ret = new JSONArray();
    for (JSONObject jsonObject: jsonObjects) {
      ret.add(wrapCluster(jsonObject));
    }
    return ret;
  }

  public JSONObject getSecondCluster(AggParam aggParam) {
    JSONObject ret = new JSONObject();

    return ret;
  }
  public JSONObject temp(AggParam aggParam) {
    JSONObject ret = new JSONObject();
    try {
      SearchResponse<News> fssj = client.search(SearchRequest.of(s -> s.index(index).query(
          q -> q.bool(
              b -> b.filter(m -> m.range(
                  r -> r.field("fssj").from("2022-04-01").to("2022-04-30")
              ))))
          .sort(SortOptions.of(k->k.field(f->f.field("fssj").order(SortOrder.Asc))))), News.class);
      System.out.println(fssj);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    try {

      SearchRequest query = SearchRequest.of(s->s.index("t_twitter_post").query(
          q->q.bool(
              b->b.should(x->x.wildcard(m->m.field("author_name").wildcard("*Josh*")
              )).should(x->x.wildcard(m->m.field("post_content").wildcard("first*")

              )).minimumShouldMatch("1").filter(m -> m.range(
                  r -> r.field("fssj").lte(JsonData.of("2022-04-01")).gte(JsonData.of("2022-04-30"))
              )))
      ) .sort(SortOptions.of(k->k.field(f->f.field("fssj").order(SortOrder.Asc))))
      );


      GetResponse<News> mapGetResponse = client.get(
          GetRequest.of(x -> x.index(index).id("1f2f2e09ed6dec42fe7b2d0db9ebcc3b")
              ), News.class);
      News source = mapGetResponse.source();
      System.out.println(source);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    try {
      List<String> ids = Arrays.asList("24c46ba9a296b7efe2b3faf4403630c2", "1f2f2e09ed6dec42fe7b2d0db9ebcc3b");
      MgetResponse<News> mget = client.mget(MgetRequest.of(m -> m.index(index).ids(ids)),
          News.class);
      List<MultiGetResponseItem<News>> docs = mget.docs();
      for (MultiGetResponseItem<News> m: docs) {
        News source = m.result().source();
        System.out.println(source);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return ret;
  }



}
